#include <list>
#include <nice2d/components/statemachine.h>

class StateData
{
public:
    std::string name;
    bool running = false;
    std::weak_ptr<StateMachine> statemachine;
};

State::State(const std::string &name):
    data(new StateData)
{
    data->name = name;
}

State::~State()
{
}

std::string State::getStateName() const
{
    return data->name;
}

bool State::isRunning() const
{
    return data->running;
}

void State::enter()
{
    data->running = true;
    onEnter();
}

void State::exit()
{
    data->running = false;
    onExit();
}

void State::update(float deltaTime)
{
}

void State::onEnter()
{
}

void State::onExit()
{
}

class StateMachineData
{
public:
    bool running = false;
    std::shared_ptr<State> initState;
    std::shared_ptr<State> currentState;

    struct StateTransition
    {
        std::shared_ptr<State> src;
        std::shared_ptr<State> dst;
        std::function<bool()> functor;
    };

    std::list<StateTransition> transitions;
};

IMPLEMENT_OBJECT(StateMachine,Component)

StateMachine::StateMachine():
    data(new StateMachineData)
{
}

StateMachine::~StateMachine()
{   
}

void StateMachine::setInitialState(std::shared_ptr<State> state)
{
    if(state && !data->running)
        data->initState = state;
}

void StateMachine::addTransition(std::shared_ptr<State> src, std::shared_ptr<State> dst, std::function<bool ()> functor)
{
    if(src && dst && functor)
    {
        data->transitions.push_back({src,dst,functor});
    }
}

void StateMachine::switchState(const std::string &src, const std::string &dst, std::function<bool ()> functor)
{
    if(!data->running || !data->currentState)
        return;

    if(data->currentState->getStateName() != src)
        return;

    auto itr = data->transitions.begin();
    while(itr != data->transitions.end())
    {
        if(data->currentState == itr->src && itr->dst->getStateName() == dst)
        {
            if(functor())
            {
                data->currentState->exit();
                data->currentState = itr->dst;
                data->currentState->enter();
                return;
            }
        }
        itr ++;
    }
}

bool StateMachine::start()
{
    if(data->running || !data->initState)
        return false;

    data->running = true;
    data->currentState = data->initState;
    data->currentState->enter();
    return true;
}

bool StateMachine::isRunning() const
{
    return data->running;
}

void StateMachine::stop()
{
    if(data->running)
    {
        data->running = false;
        data->currentState->exit();
        data->currentState.reset();
    }
}

std::shared_ptr<State> StateMachine::getRunningState()
{
    return data->currentState;
}

void StateMachine::update(float deltaTime)
{
    if(!data->running)
        return;

    if(data->currentState)
        data->currentState->update(deltaTime);

    auto itr = data->transitions.begin();
    while(itr != data->transitions.end())
    {
        if(data->currentState == itr->src)
        {
            if(itr->functor())
            {
                data->currentState->exit();
                data->currentState = itr->dst;
                data->currentState->enter();
            }
        }
        itr ++;
    }
}
